Skip to content

plugin stats implementation#220

Open
TChukwuleta wants to merge 5 commits into
masterfrom
ft/plugin_Stats
Open

plugin stats implementation#220
TChukwuleta wants to merge 5 commits into
masterfrom
ft/plugin_Stats

Conversation

@TChukwuleta
Copy link
Copy Markdown
Collaborator

@TChukwuleta TChukwuleta commented May 4, 2026

This PR introduces plugin stats on the plugin builder..

Tracks plugin installs, updates, and uninstalls across BTCPay servers plugins.

New table plugin_server_installs stores one row per server/plugin pair, updated on every download and periodic snapshot report from BTCPay.

New endpoint POST /api/v1/telemetry/plugins receives the full installed plugin list from BTCPay periodically and diffs it against stored state to detect changes.

New endpoint GET /api/v1/plugins/{slug}/stats returns total installs, active installs, uninstalls, and version breakdowns for a plugin.

Server IPs are hashed before storage.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Review Change Stack

Warning

Rate limit exceeded

@TChukwuleta has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 18 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 819b0754-47e9-4ea9-8e5d-8256ebb444a5

📥 Commits

Reviewing files that changed from the base of the PR and between 76b6132 and 62103c4.

📒 Files selected for processing (2)
  • PluginBuilder/Controllers/ApiController.cs
  • PluginBuilder/Services/TelemetryService.cs

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'auto_resolve_threads'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

This pull request introduces a plugin telemetry tracking system for the BTCPay Server Plugin Builder. It adds database persistence (plugin_server_installs table) to record plugin downloads and server-wide installation snapshots, a new TelemetryService that handles IP anonymization via hashing, BTCPay version extraction from user agents, and statistics aggregation, and two new API endpoints for clients to report their installed plugins and retrieve per-plugin metrics. The download handler is also instrumented to record telemetry for individual downloads.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • thgO-O
  • rockstardev
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'plugin stats implementation' accurately describes the main change—adding a comprehensive telemetry system to track and report plugin installation and download statistics.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description accurately describes the changeset, explaining the new plugin stats tracking feature, database table, endpoints, and IP hashing implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ft/plugin_Stats

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@TChukwuleta TChukwuleta marked this pull request as ready for review May 20, 2026 13:32
@TChukwuleta TChukwuleta requested a review from thgO-O May 20, 2026 13:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@PluginBuilder/Controllers/ApiController.cs`:
- Around line 224-226: The GetPluginStats anonymous endpoint in ApiController
(method GetPluginStats) lacks the public API rate limiting attribute; add the
[EnableRateLimiting(Policies.PublicApiRateLimit)] attribute to the
GetPluginStats action (above the method, alongside [AllowAnonymous] and
[HttpGet("plugins/{pluginSlug}/stats")]) so it uses the same PublicApiRateLimit
policy as other public read endpoints.
- Around line 217-218: The code is mapping snapshot Identifier into telemetry
Slug, which fragments per-plugin stats; change the projection that builds
pluginReports so PluginReport is constructed with the plugin's canonical slug
(e.g., p.Slug or the field used by download telemetry) instead of p.Identifier —
e.g., replace new PluginReport(p.Identifier, p.Version) with new
PluginReport(p.Slug ?? p.Identifier, p.Version) or, preferably, new
PluginReport(p.Slug, p.Version) to ensure telemetry Slug comes from the
download/telemetry slug field rather than the snapshot Identifier.
- Around line 217-221: The current code in ApiController builds pluginReports
and always calls telemetryService.RecordServerSnapshot even when pluginReports
is empty; change this by adding a guard: after building pluginReports (the
variable), check if pluginReports.Any() (or Count > 0) and only call
telemetryService.RecordServerSnapshot(remoteIp, userAgent, pluginReports,
xOriginalFor, xForwardedFor) when non-empty, otherwise skip the call and still
return Ok(); this prevents sending empty telemetry that marks installs as
uninstalled.

In `@PluginBuilder/Services/TelemetryService.cs`:
- Around line 139-161: The uninstall SQL currently decrements install_count
(plugin_server_installs.install_count) which makes GetStats (method GetStats
returning PluginStats) undercount total installs; instead stop mutating
install_count on uninstall and introduce/maintain separate cumulative counters
(e.g., cumulative_installs and cumulative_uninstalls) or an events table for
installs/uninstalls; update the uninstall UPDATE that sets uninstalled_at to no
longer decrement install_count and to increment the cumulative_uninstalls
counter (or insert an uninstall event), and change the GetStats query to compute
TotalInstalls and TotalUninstalls from those cumulative columns/events while
deriving ActiveInstalls from COUNT(*) FILTER (WHERE uninstalled_at IS NULL)
(references: plugin_server_installs, install_count, uninstalled_at, GetStats,
PluginStats).
- Around line 88-131: The payload may contain duplicate plugin.Slug values
causing the upsert loop to attempt an INSERT twice and violate the (hashed_ip,
plugin_slug) PK; before computing reportedSlugs and iterating, deduplicate
pluginList by Slug using a case-insensitive grouping (so reportedSlugs and the
foreach use the deduped collection), then proceed with the existing
existingBySlug/foreach logic referencing pluginList and plugin.Slug; this
ensures each slug is processed exactly once and avoids duplicate INSERT
attempts.
- Around line 218-223: The current logic in TelemetryService that trims
forwarded IPs (the raw variable before IPAddress.TryParse) incorrectly splits
plain IPv6 addresses; update the parsing so bracketed addresses are handled by
extracting text between '[' and ']' when raw.Contains("]:")/raw.Contains(']'),
and only strip a trailing port by splitting on ':' when the string is an IPv4
with a port (detect by presence of '.' or by there being exactly one ':'),
otherwise leave raw intact for IPv6 (multiple ':'s). Modify the block that
currently uses raw.Contains(':') && !raw.Contains('.') to instead detect
IPv4-with-port (e.g., raw.Contains('.') || raw.Count(c=>c==':')==1) before
splitting so IPv6 addresses reach IPAddress.TryParse unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b7fc9523-5951-4cc4-af82-3ff61269f733

📥 Commits

Reviewing files that changed from the base of the PR and between 05011e0 and 76b6132.

📒 Files selected for processing (7)
  • PluginBuilder/APIModels/InstalledPluginRequest.cs
  • PluginBuilder/Controllers/ApiController.cs
  • PluginBuilder/Data/Scripts/22.IncludePluginStatesTable.sql
  • PluginBuilder/DataModels/PluginDownload.cs
  • PluginBuilder/Program.cs
  • PluginBuilder/Services/TelemetryService.cs
  • PluginBuilder/ViewModels/Plugin/PluginTelemetryRequest.cs

Comment on lines +217 to +218
var pluginReports = plugins.Where(p => !string.IsNullOrWhiteSpace(p.Identifier) && !string.IsNullOrWhiteSpace(p.Version))
.Select(p => new PluginReport(p.Identifier, p.Version)).ToList();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t map Identifier directly to telemetry Slug.

This stores mixed keys (identifier from snapshot vs slug from download telemetry), fragmenting per-plugin stats and making /plugins/{pluginSlug}/stats inconsistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PluginBuilder/Controllers/ApiController.cs` around lines 217 - 218, The code
is mapping snapshot Identifier into telemetry Slug, which fragments per-plugin
stats; change the projection that builds pluginReports so PluginReport is
constructed with the plugin's canonical slug (e.g., p.Slug or the field used by
download telemetry) instead of p.Identifier — e.g., replace new
PluginReport(p.Identifier, p.Version) with new PluginReport(p.Slug ??
p.Identifier, p.Version) or, preferably, new PluginReport(p.Slug, p.Version) to
ensure telemetry Slug comes from the download/telemetry slug field rather than
the snapshot Identifier.

Comment thread PluginBuilder/Controllers/ApiController.cs
Comment thread PluginBuilder/Controllers/ApiController.cs
Comment thread PluginBuilder/Services/TelemetryService.cs Outdated
Comment thread PluginBuilder/Services/TelemetryService.cs
Comment thread PluginBuilder/Services/TelemetryService.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant